
$tokens = [
        # End of source.
        "END",
        
        # Operators and punctuators.  Some pair-wise order matters, e.g. (+, -)
        # and (UNARY_PLUS, UNARY_MINUS).
        "\n", ";",
        ",",
        "=",
        "?", ":", "CONDITIONAL",
        "||",
        "&&",
        "|",
        "^",
        "&",
        "==", "!=", "===", "!==",
        "<", "<=", ">=", ">",
        "<<", ">>", ">>>",
        "+", "-",
        "*", "/", "%",
        "!", "~", "UNARY_PLUS", "UNARY_MINUS",
        "++", "--",
        ".",
        "[", "]",
        "{", "}",
        "(", ")",
        
        # Nonterminal tree node type codes.
        "SCRIPT", "BLOCK", "LABEL", "FOR_IN", "CALL", "NEW_WITH_ARGS", "INDEX",
        "ARRAY_INIT", "OBJECT_INIT", "PROPERTY_INIT", "GETTER", "SETTER",
        "GROUP", "LIST",
        
        # Terminals.
        "IDENTIFIER", "NUMBER", "STRING", "REGEXP",
        
        # Keywords.
        "break",
        "case", "catch", "const", "continue",
        "debugger", "default", "delete", "do",
        "else", "enum",
        "false", "finally", "for", "function",
        "if", "in", "instanceof",
        "new", "null",
        "return",
        "switch",
        "this", "throw", "true", "try", "typeof",
        "var", "void",
        "while", "with",
]

# Operator and punctuator mapping from token to tree node type name.
$opTypeNames = {
        "\n"  => "NEWLINE",
        ';'   => "SEMICOLON",
        ','   => "COMMA",
        '?'   => "HOOK",
        ':'   => "COLON",
        '||'  => "OR",
        '&&'  => "AND",
        '|'   => "BITWISE_OR",
        '^'   => "BITWISE_XOR",
        '&'   => "BITWISE_AND",
        '===' => "STRICT_EQ",
        '=='  => "EQ",
        '='   => "ASSIGN",
        '!==' => "STRICT_NE",
        '!='  => "NE",
        '<<'  => "LSH",
        '<='  => "LE",
        '<'   => "LT",
        '>>>' => "URSH",
        '>>'  => "RSH",
        '>='  => "GE",
        '>'   => "GT",
        '++'  => "INCREMENT",
        '--'  => "DECREMENT",
        '+'   => "PLUS",
        '-'   => "MINUS",
        '*'   => "MUL",
        '/'   => "DIV",
        '%'   => "MOD",
        '!'   => "NOT",
        '~'   => "BITWISE_NOT",
        '.'   => "DOT",
        '['   => "LEFT_BRACKET",
        ']'   => "RIGHT_BRACKET",
        '{'   => "LEFT_CURLY",
        '}'   => "RIGHT_CURLY",
        '('   => "LEFT_PAREN",
        ')'   => "RIGHT_PAREN"
}

# Hash of keyword identifier to tokens index.
$keywords = {}

# Define const END, etc., based on the token names.  Also map name to index.
$consts = {}

$tokens.length.times do |i|
        t = $tokens[i]
        if /\A[a-z]/ =~ t
                $consts[t.upcase] = i
                $keywords[t] = i
        elsif /\A\W/ =~ t
                $consts[$opTypeNames[t]] = i
        else
                $consts[t] = i
        end
end

# Map assignment operators to their indexes in the tokens array.
$assignOps = ['|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%']
$assignOpsHash = {}

$assignOps.length.times do |i|
        t = $assignOps[i]
        $assignOpsHash[t] = $consts[$opTypeNames[t]]
end

$opPrecedence = {
        "SEMICOLON" => 0,
        "COMMA" => 1,
        "ASSIGN" => 2,
        "HOOK" => 3, "COLON" => 3, "CONDITIONAL" => 3,
        "OR" => 4,
        "AND" => 5,
        "BITWISE_OR" => 6,
        "BITWISE_XOR" => 7,
        "BITWISE_AND" => 8,
        "EQ" => 9, "NE" => 9, "STRICT_EQ" => 9, "STRICT_NE" => 9,
        "LT" => 10, "LE" => 10, "GE" => 10, "GT" => 10, "IN" => 10, "INSTANCEOF" => 10,
        "LSH" => 11, "RSH" => 11, "URSH" => 11,
        "PLUS" => 12, "MINUS" => 12,
        "MUL" => 13, "DIV" => 13, "MOD" => 13,
        "DELETE" => 14, "VOID" => 14, "TYPEOF" => 14, # PRE_INCREMENT: 14, PRE_DECREMENT: 14,
        "NOT" => 14, "BITWISE_NOT" => 14, "UNARY_PLUS" => 14, "UNARY_MINUS" => 14,
        "INCREMENT" => 15, "DECREMENT" => 15, # postfix
        "NEW" => 16,
        "DOT" => 17
}

# Map operator type code to precedence.
$opPrecedence.keys.each do |i|
        $opPrecedence[$consts[i]] = $opPrecedence[i]
end

$opArity = {
        "COMMA" => -2,
        "ASSIGN" => 2,
        "CONDITIONAL" => 3,
        "OR" => 2,
        "AND" => 2,
        "BITWISE_OR" => 2,
        "BITWISE_XOR" => 2,
        "BITWISE_AND" => 2,
        "EQ" => 2, "NE" => 2, "STRICT_EQ" => 2, "STRICT_NE" => 2,
        "LT" => 2, "LE" => 2, "GE" => 2, "GT" => 2, "IN" => 2, "INSTANCEOF" => 2,
        "LSH" => 2, "RSH" => 2, "URSH" => 2,
        "PLUS" => 2, "MINUS" => 2,
        "MUL" => 2, "DIV" => 2, "MOD" => 2,
        "DELETE" => 1, "VOID" => 1, "TYPEOF" => 1, # PRE_INCREMENT: 1, PRE_DECREMENT: 1,
        "NOT" => 1, "BITWISE_NOT" => 1, "UNARY_PLUS" => 1, "UNARY_MINUS" => 1,
        "INCREMENT" => 1, "DECREMENT" => 1,   # postfix
        "NEW" => 1, "NEW_WITH_ARGS" => 2, "DOT" => 2, "INDEX" => 2, "CALL" => 2,
        "ARRAY_INIT" => 1, "OBJECT_INIT" => 1, "GROUP" => 1
}

# Map operator type code to arity.
$opArity.keys.each do |i|
        $opArity[$consts[i]] = $opArity[i]
end


# NB: superstring tokens (e.g., ++) must come before their substring token
# counterparts (+ in the example), so that the $opRegExp regular expression
# synthesized from this list makes the longest possible match.
$ops = [';', ',', '?', ':', '||', '&&', '|', '^', '&', '===', '==', 
        '=', '!==', '!=', '<<', '<=', '<', '>>>', '>>', '>=', '>', '++', '--',
        '+', '-', '*', '/', '%', '!', '~', '.', '[', ']', '{', '}', '(', ')']

# Build a regexp that recognizes operators and punctuators (except newline).
$opRegExpSrc = "\\A"
$ops.length.times do |i|
        $opRegExpSrc += "|\\A" if $opRegExpSrc != "\\A"
        $opRegExpSrc += $ops[i].gsub(/([?|^&(){}\[\]+\-*\/\.])/) {|s| "\\" + s}
end
$opRegExp = Regexp.new($opRegExpSrc, Regexp::MULTILINE)


# A regexp to match floating point literals (but not integer literals).
$fpRegExp = Regexp.new("\\A\\d+\\.\\d*(?:[eE][-+]?\\d+)?|\\A\\d+(?:\\.\\d*)?[eE][-+]?\\d+|\\A\\.\\d+(?:[eE][-+]?\\d+)?", Regexp::MULTILINE)


class Tokenizer

        attr_accessor :cursor, :source, :tokens, :tokenIndex, :lookahead
        attr_accessor :scanNewlines, :scanOperand, :filename, :lineno

        def initialize (source, filename, line)
                @cursor = 0
                @source = source.to_s
                @tokens = []
                @tokenIndex = 0
                @lookahead = 0
                @scanNewlines = false
                @scanOperand = true
                @filename = filename or ""
                @lineno = line or 1
        end

        def input
                return @source.slice(@cursor, @source.length - @cursor)
        end

        def done
                return self.peek == $consts["END"];
        end

        def token
                return @tokens[@tokenIndex];
        end
        
        def match (tt)
                puts "Calling match of " + tt.to_s
                got = self.get
                puts "GOOT " + got.to_s + " " + tt.to_s
                #puts got
                #puts tt
                return got == tt || self.unget
        end
        
        def mustMatch (tt)
                print "Calling mustMatch " + tt.to_s
                raise SyntaxError.new("Missing " + $tokens[tt].downcase, self) unless self.match(tt)
                return self.token
        end

        def peek
                if @lookahead > 0
                        #tt = @tokens[(@tokenIndex + @lookahead)].type
                        tt = @tokens[(@tokenIndex + @lookahead) & 3].type
                else
                        tt = self.get
                        self.unget
                end
                return tt
        end
        
        def peekOnSameLine
                @scanNewlines = true;
                tt = self.peek
                @scanNewlines = false;
                return tt
        end

        def get
                while @lookahead > 0
                        @lookahead -= 1
                        @tokenIndex = (@tokenIndex + 1) & 3
                        token = @tokens[@tokenIndex]
                        return token.type if token.type != $consts["NEWLINE"] || @scanNewlines
                end
                
                while true
                        input = self.input
                        puts "Input => " + input
                        if @scanNewlines
                                puts "Scannewlines is true"
                                match = /\A[ \t]+/.match(input)
                        else
                                match = /\A\s+/.match(input)
                        end
                        
                        if match
                                puts "A MATCH FOUND!"
                                spaces = match[0]
                                puts "Spaces => " + spaces.length.to_s
                                @cursor += spaces.length
                                puts "Newline count => " + spaces.count("\n").to_s
                                @lineno += spaces.count("\n")
                                input = self.input
                        end
                        
                        puts "Input=> " + input + " " + input.length.to_s
                        match = /\A\/(?:\*(?:.)*?\*\/|\/[^\n]*)/m.match(input)
                        if !match:
                            puts "BREAKING"
                            break
                        end
                          
                        puts "Cursor=> " + @cursor.to_s
                        comment = match[0]
                        puts "Comment => " + comment
                        @cursor += comment.length
                        puts "Cursor=> " + @cursor.to_s
                        puts 'Comment length => ' + comment.length.to_s
                        puts "Comment newline count => " + comment.count("\n").to_s
                        @lineno += comment.count("\n")
                end
                
                #puts input
                
                @tokenIndex = (@tokenIndex + 1) & 3
                token = @tokens[@tokenIndex]
                (@tokens[@tokenIndex] = token = Token.new) unless token
                if input.length == 0
                        #puts "end!!!"
                        return (token.type = $consts["END"])
                end

                cursor_advance = 0
                if (match = $fpRegExp.match(input))
                        puts "Matched here1"            
                        token.type = $consts["NUMBER"]
                        token.value = match[0].to_f
                elsif (match = /\A0[xX][\da-fA-F]+|\A0[0-7]*|\A\d+/.match(input))
                        puts "Matched here2"            
                        token.type = $consts["NUMBER"]
                        token.value = match[0].to_i
                elsif (match = /\A(\w|\$)+/.match(input))
                        puts "MATCH: Matched here3 " + input
                        id = match[0]
                        token.type = $keywords[id] || $consts["IDENTIFIER"]
                        token.value = id
                elsif (match = /\A"(?:\\.|[^"])*"|\A'(?:[^']|\\.)*'/.match(input))
                        puts "Matched here4"            
                        token.type = $consts["STRING"]
                        token.value = match[0].to_s
                elsif @scanOperand and (match = /\A\/((?:\\.|[^\/])+)\/([gi]*)/.match(input))
                        puts "Matched here5"            
                        token.type = $consts["REGEXP"]
                        token.value = Regexp.new(match[1] ) #, match[2])
                elsif (match = $opRegExp.match(input))
                        puts "Matched here6 " + input            
                        op = match[0]
                        if $assignOpsHash[op] && input[op.length, 1] == '='
                                puts "Token type is ASSIGN"
                                token.type = $consts["ASSIGN"]
                                token.assignOp = $consts[$opTypeNames[op]]
                                cursor_advance = 1 # length of '='
                        else
                                puts "TOKEN TYPE NOT ASSIGN"
                                #puts $consts[$opTypeNames[op]].to_s + " " + $opTypeNames[op] + " " + op
                                token.type = $consts[$opTypeNames[op]]
                                puts token.type.to_s + " " + self.scanOperand.to_s + " " + $consts["MINUS"].to_s
                                if @scanOperand and (token.type == $consts["PLUS"] || token.type == $consts["MINUS"])
                                        puts "Adding to token type!"
                                        token.type += $consts["UNARY_PLUS"] - $consts["PLUS"]
                                end
                                token.assignOp = nil
                        end
                        token.value = op
                else
                        raise SyntaxError.new("Illegal token", self)
                end

                token.start = @cursor
                puts "Group0 => " + match[0]
                puts 'Advancing cursor by ' + (match[0].length + cursor_advance).to_s
                @cursor += match[0].length + cursor_advance
                token.end = @cursor
                token.lineno = @lineno
                
                puts "Returning " + token.type.to_s
                return token.type
        end

        def unget
                #puts "start: lookahead: " + @lookahead.to_s + " tokenIndex: " + @tokenIndex.to_s
                @lookahead += 1
                raise SyntaxError.new("PANIC: too much lookahead!", self) if @lookahead == 4
                @tokenIndex = (@tokenIndex - 1) & 3
                #puts "end:   lookahead: " + @lookahead.to_s + " tokenIndex: " + @tokenIndex.to_s
                return nil
        end

end

class SyntaxError
        def initialize (msg, tokenizer)
                puts msg
                puts "on line " + tokenizer.lineno.to_s
        end
end


class Token
        attr_accessor :type, :value, :start, :end, :lineno, :assignOp
end


class CompilerContext
        attr_accessor :inFunction, :stmtStack, :funDecls, :varDecls
        attr_accessor :bracketLevel, :curlyLevel, :parenLevel, :hookLevel
        attr_accessor :ecmaStrictMode, :inForLoopInit

        def initialize (inFunction)
                @inFunction = inFunction
                @stmtStack = []
                @funDecls = []
                @varDecls = []
                
                @bracketLevel = @curlyLevel = @parenLevel = @hookLevel = 0
                @ecmaStrictMode = @inForLoopInit = false
        end
end


class Node < Array

        attr_accessor :type, :value, :lineno, :start, :end, :tokenizer, :initializer
        attr_accessor :name, :params, :funDecls, :varDecls, :body, :functionForm
        attr_accessor :assignOp, :expression, :condition, :thenPart, :elsePart
        attr_accessor :readOnly, :isLoop, :setup, :postfix, :update, :exception
        attr_accessor :object, :iterator, :varDecl, :label, :target, :tryBlock
        attr_accessor :catchClauses, :varName, :guard, :block, :discriminant, :cases
        attr_accessor :defaultIndex, :caseLabel, :statements, :statement

        def initialize (t, type = nil)
                token = t.token
                if token
                        if type != nil
                                @type = type
                        else
                                @type = token.type
                        end
                        @value = token.value
                        @lineno = token.lineno
                        @start = token.start
                        @end = token.end
                else
                        @type = type
                        @lineno = t.lineno
                end
                @tokenizer = t
                #for (var i = 2; i < arguments.length; i++)
                #this.push(arguments[i]);
        end

        alias superPush push
        # Always use push to add operands to an expression, to update start and end.
        def push (kid)
                if kid.start and @start
                        @start = kid.start if kid.start < @start
                end
                if kid.end and @end
                        @end = kid.end if @end < kid.end
                end
                return superPush(kid)
        end

#       def to_s
#               a = []
#               
#               #for (var i in this) {
#               #       if (this.hasOwnProperty(i) && i != 'type')
#               #               a.push({id: i, value: this[i]});
#               #}
#               #a.sort(function (a,b) { return (a.id < b.id) ? -1 : 1; });
#               iNDENTATION = "    "
#               n = (Node.indentLevel += 1)
#               t = $tokens[@type]
#               s = "{\n" + iNDENTATION.repeat(n) +
#                               "type: " + (/^\W/.test(t) and opTypeNames[t] or t.upcase)
#               #for (i = 0; i < a.length; i++)
#               #       s += ",\n" + INDENTATION.repeat(n) + a[i].id + ": " + a[i].value
#                       s += ",\n" + iNDENTATION.repeat(n) + @value + ": " + a[i].value
#               n = (Node.indentLevel -= 1)
#               s += "\n" + iNDENTATION.repeat(n) + "}"
#               return s
#       end

        def to_s
        
                attrs = [@value,
                        @lineno, @start, @end,
                        @name, @params, @funDecls, @varDecls, @body, @functionForm,
                        @assignOp, @expression, @condition, @thenPart, @elsePart]
                
                #puts $tokens[@condition.type] if @condition != nil
                
                #if /\A[a-z]/ =~ $tokens[@type] # identifier
                #       print @tokenizer.source.slice($cursor, @start - $cursor) if $cursor < @start
                #       print '<span class="identifier">'
                #       print @tokenizer.source.slice(@start, $tokens[@type].length)
                #       print '</span>'
                #       $cursor = @start + $tokens[@type].length
                #end
                
                #puts (" " * $ind) + "{" + $tokens[@type] + "\n" if /\A[a-z]/ =~ $tokens[@type]
                #puts (" " * $ind) + " " + @start.to_s + "-" + @end.to_s + "\n"
                $ind += 1
                #puts @value
                self.length.times do |i|
                        self[i].to_s if self[i] != self and self[i].class == Node
                end
                attrs.length.times do |attr|
                        if $tokens[@type] == "if"
                        #       puts $tokens[attrs[attr].type] if attrs[attr].class == Node and attrs[attr] !== self
                        end
                        attrs[attr].to_s if attrs[attr].class == Node #and attrs[attr] != self
                        #puts (" " * $ind).to_s + attrs[attr].to_s if attrs[attr].to_s != nil and attrs[attr] != self
                end
                $ind -= 1
                #puts "\n}\n"
                
                if $ind == 0
                        print @tokenizer.source.slice($cursor, @tokenizer.source.length - $cursor)
                end
                
                return ""
        end

        def getSource
                return @tokenizer.source.slice(@start, @end)
        end

        def filename
                return @tokenizer.filename
        end
end

$cursor = 0

$ind = 0

def Script (t, x)
        n = Statements(t, x)
        n.type = $consts["SCRIPT"]
        n.funDecls = x.funDecls
        n.varDecls = x.varDecls
        return n
end


# Statement stack and nested statement handler.
# nb. Narcissus allowed a function reference, here we use Statement explicitly
def nest (t, x, node, end_ = nil)
        x.stmtStack.push(node)
        n = Statement(t, x)
        x.stmtStack.pop
        end_ and t.mustMatch(end_)
        return n
end


def Statements (t, x)
        n = Node.new(t, $consts["BLOCK"])
        x.stmtStack.push(n)
        n.push(Statement(t, x)) while !t.done and t.peek != $consts["RIGHT_CURLY"]
        x.stmtStack.pop
        return n
end


def Block (t, x)
        t.mustMatch($consts["LEFT_CURLY"])
        n = Statements(t, x)
        t.mustMatch($consts["RIGHT_CURLY"])
        return n
end


DECLARED_FORM = 0
EXPRESSED_FORM = 1
STATEMENT_FORM = 2

def Statement (t, x)
        tt = t.get
        puts "TT is " + tt.to_s
        # Cases for statements ending in a right curly return early, avoiding the
        # common semicolon insertion magic after this switch.
        case tt
                when $consts["FUNCTION"]
                       puts "TT is a function"
                        return FunctionDefinition(t, x, true, 
                                (x.stmtStack.length > 1) && STATEMENT_FORM || DECLARED_FORM)

                when $consts["LEFT_CURLY"]
                        n = Statements(t, x)
                        t.mustMatch($consts["RIGHT_CURLY"])
                        return n
                
                when $consts["IF"]
                        n = Node.new(t)
                        n.condition = ParenExpression(t, x)
                        x.stmtStack.push(n)
                        n.thenPart = Statement(t, x)
                        n.elsePart = t.match($consts["ELSE"]) ? Statement(t, x) : nil
                        x.stmtStack.pop()
                        return n

                when $consts["SWITCH"]
                        n = Node.new(t)
                        t.mustMatch($consts["LEFT_PAREN"])
                        n.discriminant = Expression(t, x)
                        t.mustMatch($consts["RIGHT_PAREN"])
                        n.cases = []
                        n.defaultIndex = -1
                        x.stmtStack.push(n)
                        t.mustMatch($consts["LEFT_CURLY"])
                        while (tt = t.get) != $consts["RIGHT_CURLY"]
                                case tt
                                        when $consts["DEFAULT"], $consts["CASE"]
                                                if tt == $consts["DEFAULT"] and n.defaultIndex >= 0
                                                        raise SyntaxError.new("More than one switch default", t)
                                                end
                                                n2 = Node.new(t)
                                                if tt == $consts["DEFAULT"]
                                                        n.defaultIndex = n.cases.length
                                                else
                                                        n2.caseLabel = Expression(t, x, $consts["COLON"])
                                                end
                                        
                                        else
                                                raise SyntaxError.new("Invalid switch case", t)
                                end
                                t.mustMatch($consts["COLON"])
                                n2.statements = Node.new(t, $consts["BLOCK"])
                                while (tt = t.peek) != $consts["CASE"] and tt != $consts["DEFAULT"] and tt != $consts["RIGHT_CURLY"]
                                        n2.statements.push(Statement(t, x))
                                end
                                n.cases.push(n2)
                        end
                        x.stmtStack.pop
                        return n
                
                when $consts["FOR"]
                        n = Node.new(t)
                        n.isLoop = true
                        t.mustMatch($consts["LEFT_PAREN"])
                        if (tt = t.peek) != $consts["SEMICOLON"]
                                x.inForLoopInit = true
                                if tt == $consts["VAR"] or tt == $consts["CONST"]
                                        t.get
                                        n2 = Variables(t, x)
                                else
                                        n2 = Expression(t, x)
                                end
                                x.inForLoopInit = false
                        end
                        if n2 and t.match($consts["IN"])
                                n.type = $consts["FOR_IN"]
                                if n2.type == $consts["VAR"]
                                        if n2.length != 1
                                                raise SyntaxError.new("Invalid for..in left-hand side", t)
                                        end
                                        # NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name.
                                        n.iterator = n2[0]
                                        n.varDecl = n2
                                else
                                        n.iterator = n2
                                        n.varDecl = nil
                                end
                                n.object = Expression(t, x)
                        else
                                n.setup = n2 or nil
                                t.mustMatch($consts["SEMICOLON"])
                                n.condition = (t.peek == $consts["SEMICOLON"]) ? nil : Expression(t, x)
                                t.mustMatch($consts["SEMICOLON"])
                                n.update = (t.peek == $consts["RIGHT_PAREN"]) ? nil : Expression(t, x)
                        end
                        t.mustMatch($consts["RIGHT_PAREN"])
                        n.body = nest(t, x, n)
                        return n
                
                when $consts["WHILE"]
                        n = Node.new(t)
                        n.isLoop = true
                        n.condition = ParenExpression(t, x)
                        n.body = nest(t, x, n)
                        return n
                
                when $consts["DO"]
                        n = Node.new(t)
                        n.isLoop = true
                        n.body = nest(t, x, n, $consts["WHILE"])
                        n.condition = ParenExpression(t, x)
                        if !x.ecmaStrictMode
                                # <script language="JavaScript"> (without version hints) may need
                                # automatic semicolon insertion without a newline after do-while.
                                # See http://bugzilla.mozilla.org/show_bug.cgi?id=238945.
                                t.match($consts["SEMICOLON"])
                                return n
                        end
                
                when $consts["BREAK"], $consts["CONTINUE"]
                        n = Node.new(t)
                        if t.peekOnSameLine == $consts["IDENTIFIER"]
                                t.get
                                n.label = t.token.value
                        end
                        ss = x.stmtStack
                        i = ss.length
                        label = n.label
                        if label
                                begin
                                        i -= 1
                                        raise SyntaxError.new("Label not found", t) if i < 0
                                end while (ss[i].label != label)
                        else
                                begin
                                        i -= 1
                                        raise SyntaxError.new("Invalid " + ((tt == $consts["BREAK"]) and "break" or "continue"), t) if i < 0
                                end while !ss[i].isLoop and (tt != $consts["BREAK"] or ss[i].type != $consts["SWITCH"])
                        end
                        n.target = ss[i]
                
                when $consts["TRY"]
                        n = Node.new(t)
                        n.tryBlock = Block(t, x)
                        n.catchClauses = []
                        while t.match($consts["CATCH"])
                                n2 = Node.new(t)
                                t.mustMatch($consts["LEFT_PAREN"])
                                n2.varName = t.mustMatch($consts["IDENTIFIER"]).value
                                if t.match($consts["IF"])
                                        raise SyntaxError.new("Illegal catch guard", t) if x.ecmaStrictMode
                                        if n.catchClauses.length and !n.catchClauses.last.guard
                                                raise SyntaxError.new("Guarded catch after unguarded", t)
                                        end
                                        n2.guard = Expression(t, x)
                                else
                                        n2.guard = nil
                                end
                                t.mustMatch($consts["RIGHT_PAREN"])
                                n2.block = Block(t, x)
                                n.catchClauses.push(n2)
                        end
                        n.finallyBlock = Block(t, x) if t.match($consts["FINALLY"])
                        if !n.catchClauses.length and !n.finallyBlock
                                raise SyntaxError.new("Invalid try statement", t)
                        end
                        return n
                
                when $consts["CATCH"]
                when $consts["FINALLY"]
                        raise SyntaxError.new(tokens[tt] + " without preceding try", t)
                
                when $consts["THROW"]
                        n = Node.new(t)
                        n.exception = Expression(t, x)
                
                when $consts["RETURN"]
                        puts "In Return"
                        raise SyntaxError.new("Invalid return", t) unless x.inFunction
                        n = Node.new(t)
                        tt = t.peekOnSameLine
                        if tt != $consts["END"] and tt != $consts["NEWLINE"] and tt != $consts["SEMICOLON"] and tt != $consts["RIGHT_CURLY"]
                                n.value = Expression(t, x)
                                puts "Val => " + n.value.to_s
                        end
                
                when $consts["WITH"]
                        n = Node.new(t)
                        n.object = ParenExpression(t, x)
                        n.body = nest(t, x, n)
                        return n
                
                when $consts["VAR"], $consts["CONST"]
                        n = Variables(t, x)
                
                when $consts["DEBUGGER"]
                        n = Node.new(t)
        
                when $consts["NEWLINE"], $consts["SEMICOLON"]
                        n = Node.new(t, $consts["SEMICOLON"])
                        n.expression = nil
                        return n

                else
                        if tt == $consts["IDENTIFIER"] and t.peek == $consts["COLON"]
                                label = t.token.value
                                ss = x.stmtStack
                                (ss.length - 1).times do |i|
                                        raise SyntaxError.new("Duplicate label", t) if ss[i].label == label
                                end
                                t.get
                                n = Node.new(t, $consts["LABEL"])
                                n.label = label
                                n.statement = nest(t, x, n)
                                return n
                        end

                        t.unget
                        n = Node.new(t, $consts["SEMICOLON"])
                        n.expression = Expression(t, x)
                        n.end = n.expression.end
        end

        if t.lineno == t.token.lineno
                tt = t.peekOnSameLine
                puts "TT*=> " + tt.to_s
                if tt != $consts["END"] and tt != $consts["NEWLINE"] and tt != $consts["SEMICOLON"] and tt != $consts["RIGHT_CURLY"]
                        raise SyntaxError.new("Missing ; before statement", t)
                end
        end
        t.match($consts["SEMICOLON"])
        return n
end


def FunctionDefinition (t, x, requireName, functionForm)
        f = Node.new(t)
        if f.type != $consts["FUNCTION"]
                f.type = (f.value == "get") and $consts["GETTER"] or $consts["SETTER"]
        end
        if t.match($consts["IDENTIFIER"])
                f.name = t.token.value
        elsif requireName
                raise SyntaxError.new("Missing function identifier", t)
        end
        t.mustMatch($consts["LEFT_PAREN"])
        f.params = []
        while (tt = t.get) != $consts["RIGHT_PAREN"]
                raise SyntaxError.new("Missing formal parameter", t) unless tt == $consts["IDENTIFIER"]
                f.params.push(t.token.value)
                t.mustMatch($consts["COMMA"]) unless t.peek == $consts["RIGHT_PAREN"]
        end
        
        t.mustMatch($consts["LEFT_CURLY"])
        x2 = CompilerContext.new(true)
        f.body = Script(t, x2)
        t.mustMatch($consts["RIGHT_CURLY"])
        f.end = t.token.end
        f.functionForm = functionForm
        x.funDecls.push(f) if functionForm == $consts["DECLARED_FORM"]
        return f
end


def Variables (t, x)
        n = Node.new(t)

        begin
                t.mustMatch($consts["IDENTIFIER"])
                n2 = Node.new(t)
                n2.name = n2.value
                if t.match($consts["ASSIGN"])
                        raise SyntaxError.new("Invalid variable initialization", t) if t.token.assignOp
                        n2.initializer = Expression(t, x, $consts["COMMA"])
                end
                n2.readOnly = (n.type == $consts["CONST"])
                n.push(n2)
                x.varDecls.push(n2)
        end while t.match($consts["COMMA"])
        return n
end


def ParenExpression (t, x)
        t.mustMatch($consts["LEFT_PAREN"])
        n = Expression(t, x)
        t.mustMatch($consts["RIGHT_PAREN"])
        return n
end


def Expression(t, x, stop = nil)
        operators = []
        operands = []
        bl = x.bracketLevel
        cl = x.curlyLevel
        pl = x.parenLevel
        hl = x.hookLevel
        
        def reduce(operators, operands, t)
                #  puts "Operators length => " + operators.length.to_s
                n = operators.pop
                puts "N => " + n.to_s
                op = n.type
                arity = $opArity[op]
                puts "Arity=> " + arity.to_s
                if arity == -2
                        if operands.length >= 2
                                # Flatten left-associative trees.
                                left = operands[operands.length - 2]
                                puts "Left=> " + left.to_s

                                if left.type == op
                                        puts "Dude!"
                                        right = operands.pop
                                        left.push(right)
                                        return left
                                end
                        end
                        arity = 2
                end
                
                # Always use push to add operands to n, to update start and end.
                a = operands.slice!(operands.length - arity, operands.length)

                arity.times do |i|
                        n.push(a[i])
                end
                
                # Include closing bracket or postfix operator in [start,end).
                n.end = t.token.end if n.end < t.token.end
                
                operands.push(n)
                return n
        end

gotoloopContinue = false
until gotoloopContinue or (t.token and t.token.type == $consts["END"])
gotoloopContinue = catch(:gotoloop) do
#loop:
        while true
                puts "Before getting TT"
                if ((tt = t.get) == $consts["END"])
                  break
                 end
                puts "TT ==> " + tt.to_s
                # Stop only if tt matches the optional stop parameter, and that
                # token is not quoted by some kind of bracket.
                if tt == stop and x.bracketLevel == bl and x.curlyLevel == cl and x.parenLevel == pl and x.hookLevel == hl
                        throw :gotoloop, true
                end
                
                case tt
                        when $consts["SEMICOLON"]
                                # NB: cannot be empty, Statement handled that.
                                throw :gotoloop, true;
                        
                        when $consts["ASSIGN"], $consts["HOOK"], $consts["COLON"]
                                if t.scanOperand
                                        throw :gotoloop, true
                                end
                                                                
                                # Use >, not >=, for right-associative ASSIGN and HOOK/COLON.
                                while operators.length > 0 && $opPrecedence[operators.last.type] && $opPrecedence[operators.last.type] > $opPrecedence[tt]
                                        reduce(operators, operands, t)
                                end
                                if tt == $consts["COLON"]
                                        n = operators.last
                                        raise SyntaxError.new("Invalid label", t) if n.type != $consts["HOOK"]
                                        n.type = $consts["CONDITIONAL"]
                                        x.hookLevel -= 1
                                else
                                        operators.push(Node.new(t))
                                        if tt == $consts["ASSIGN"]
                                                operands.last.assignOp = t.token.assignOp
                                        else
                                                x.hookLevel += 1 # tt == HOOK
                                        end
                                end
                                t.scanOperand = true
                        
                        when $consts["COMMA"],
                                # Treat comma as left-associative so reduce can fold left-heavy
                                # COMMA trees into a single array.
                                $consts["OR"], $consts["AND"], $consts["BITWISE_OR"], $consts["BITWISE_XOR"],
                                $consts["BITWISE_AND"], $consts["EQ"], $consts["NE"], $consts["STRICT_EQ"],
                                $consts["STRICT_NE"], $consts["LT"], $consts["LE"], $consts["GE"],
                                $consts["GT"], $consts["INSTANCEOF"], $consts["LSH"], $consts["RSH"],
                                $consts["URSH"], $consts["PLUS"], $consts["MINUS"], $consts["MUL"],
                                $consts["DIV"], $consts["MOD"], $consts["DOT"], $consts["IN"]

                                # An in operator should not be parsed if we're parsing the head of
                                # a for (...) loop, unless it is in the then part of a conditional
                                # expression, or parenthesized somehow.
                                if tt == $consts["IN"] and x.inForLoopInit and x.hookLevel == 0 and x.bracketLevel == 0 and x.curlyLevel == 0 and x.parenLevel == 0
                                        throw :gotoloop, true
                                end
                                
                                if t.scanOperand
                                        throw :gotoloop, true
                                end

                                reduce(operators, operands, t) while operators.length > 0 && $opPrecedence[operators.last.type] && $opPrecedence[operators.last.type] >= $opPrecedence[tt]

                                if tt == $consts["DOT"]
                                        t.mustMatch($consts["IDENTIFIER"])
                                        node = Node.new(t, $consts["DOT"])
                                        node.push(operands.pop)
                                        node.push(Node.new(t))
                                        operands.push(node)
                                else
                                        operators.push(Node.new(t))
                                        t.scanOperand = true
                                end
                        
                        when $consts["DELETE"], $consts["VOID"], $consts["TYPEOF"], $consts["NOT"],
                                $consts["BITWISE_NOT"], $consts["UNARY_PLUS"], $consts["UNARY_MINUS"],
                                $consts["NEW"]

                                if !t.scanOperand
                                        throw :gotoloop, true
                                end
                                operators.push(Node.new(t))
                        
                        when $consts["INCREMENT"], $consts["DECREMENT"]
                                if t.scanOperand
                                        operators.push(Node.new(t)) # prefix increment or decrement
                                else
                                        # Use >, not >=, so postfix has higher precedence than prefix.
                                        reduce(operators, operands, t) while operators.length > 0 && $opPrecedence[operators.last.type] && $opPrecedence[operators.last.type] > $opPrecedence[tt]
                                        n = Node.new(t, tt)
                                        n.push(operands.pop)
                                        n.postfix = true
                                        operands.push(n)
                                end
                        
                        when $consts["FUNCTION"]
                                if !t.scanOperand
                                        throw :gotoloop, true
                                end
                                # puts "EXPRESSED FORM=> " + $consts["EXPRESSED_FORM"]
                                operands.push(FunctionDefinition(t, x, false, $consts["EXPRESSED_FORM"]))
                                t.scanOperand = false
                        
                        when $consts["NULL"], $consts["THIS"], $consts["TRUE"], $consts["FALSE"],
                                $consts["IDENTIFIER"], $consts["NUMBER"], $consts["STRING"],
                                $consts["REGEXP"]

                                if !t.scanOperand
                                        puts "THrowing"
                                        throw :gotoloop, true
                                end
                                puts "No throw"
                                operands.push(Node.new(t))
                                t.scanOperand = false
                        
                        when $consts["LEFT_BRACKET"]
                                if t.scanOperand
                                        # Array initialiser.  Parse using recursive descent, as the
                                        # sub-grammar here is not an operator grammar.
                                        n = Node.new(t, $consts["ARRAY_INIT"])
                                        while (tt = t.peek) != $consts["RIGHT_BRACKET"]
                                                if tt == $consts["COMMA"]
                                                        t.get
                                                        n.push(nil)
                                                        next
                                                end
                                                n.push(Expression(t, x, $consts["COMMA"]))
                                                break if !t.match($consts["COMMA"])
                                        end
                                        t.mustMatch($consts["RIGHT_BRACKET"])
                                        operands.push(n)
                                        t.scanOperand = false
                                else
                                        # Property indexing operator.
                                        operators.push(Node.new(t, $consts["INDEX"]))
                                        t.scanOperand = true
                                        x.bracketLevel += 1
                                end
                        
                        when $consts["RIGHT_BRACKET"]
                                if t.scanOperand or x.bracketLevel == bl
                                        throw :gotoloop, true
                                end
                                while reduce(operators, operands, t).type != $consts["INDEX"]
                                        nil
                                end
                                x.bracketLevel -= 1
                        
                        when $consts["LEFT_CURLY"]
                                if !t.scanOperand
                                        throw :gotoloop, true
                                end
                                # Object initialiser.  As for array initialisers (see above),
                                # parse using recursive descent.
                                x.curlyLevel += 1
                                n = Node.new(t, $consts["OBJECT_INIT"])

catch(:gotoobject_init) do
#object_init:
                                if !t.match($consts["RIGHT_CURLY"])
                                        begin
                                                tt = t.get
                                                if (t.token.value == "get" or t.token.value == "set") and t.peek == $consts["IDENTIFIER"]
                                                        raise SyntaxError.new("Illegal property accessor", t) if x.ecmaStrictMode
                                                        n.push(FunctionDefinition(t, x, true, $consts["EXPRESSED_FORM"]))
                                                else
                                                        case tt
                                                                when $consts["IDENTIFIER"], $consts["NUMBER"], $consts["STRING"]
                                                                        id = Node.new(t)
                                                                
                                                                when $consts["RIGHT_CURLY"]
                                                                        raise SyntaxError.new("Illegal trailing ,", t) if x.ecmaStrictMode
                                                                        throw :gotoobject_init
                                                                
                                                                else
                                                                        raise SyntaxError.new("Invalid property name", t)
                                                        end
                                                        t.mustMatch($consts["COLON"])
                                                        n2 = Node.new(t, $consts["PROPERTY_INIT"])
                                                        n2.push(id)
                                                        n2.push(Expression(t, x, $consts["COMMA"]))
                                                        n.push(n2)
                                                end
                                        end while t.match($consts["COMMA"])
                                        t.mustMatch($consts["RIGHT_CURLY"])
                                end
                                operands.push(n)
                                t.scanOperand = false
                                x.curlyLevel -= 1
end

                        when $consts["RIGHT_CURLY"]
                                raise SyntaxError.new("PANIC: right curly botch", t) if !t.scanOperand and x.curlyLevel != cl
                                throw :gotoloop, true
                        
                        when $consts["LEFT_PAREN"]
                                if t.scanOperand
                                        operators.push(Node.new(t, $consts["GROUP"]))
                                else
                                        reduce(operators, operands, t) while operators.length > 0 && $opPrecedence[operators.last.type] && $opPrecedence[operators.last.type] > $opPrecedence[$consts["NEW"]]
                                        # Handle () now, to regularize the n-ary case for n > 0.
                                        # We must set scanOperand in case there are arguments and
                                        # the first one is a regexp or unary+/-.
                                        n = operators.last
                                        puts n.to_s + " " + n.type.to_s + " " + $consts["NEW"].to_s
                                        t.scanOperand = true
                                        puts "I WENT HERE"
                                        if t.match($consts["RIGHT_PAREN"])
                                                if n && n.type == $consts["NEW"]
                                                        puts "INSIDE"
                                                        operators.pop
                                                        n.push(operands.pop)
                                                        puts "AFTER*"
                                                else
                                                        puts "OUTSIDE"
                                                        n = Node.new(t, $consts["CALL"])
                                                        n.push(operands.pop)
                                                        n.push(Node.new(t, $consts["LIST"]))
                                                end
                                                puts "AFTER2*"
                                                operands.push(n)
                                                t.scanOperand = false
                                                #puts "woah"
                                                puts "BEFOREBREAK"
                                                break
                                        end
                                        if n && n.type == $consts["NEW"]
                                                n.type = $consts["NEW_WITH_ARGS"]
                                        else
                                                operators.push(Node.new(t, $consts["CALL"]))
                                        end
                                end
                                x.parenLevel += 1
                                
                        when $consts["RIGHT_PAREN"]
                                if t.scanOperand or x.parenLevel == pl
                                        throw :gotoloop, true
                                end
                                
                                #while (tt = reduce(operators, operands, t).type) != $consts["GROUP"] \
                                #                and tt != $consts["CALL"] and tt != $consts["NEW_WITH_ARGS"]
                                #        nil
                                #end

                                while true
                                      puts "Operators => " + operators.length.to_s + " " + operands.length.to_s
                                      tt = reduce(operators, operands, t)
                                      puts "TT=> " + tt.to_s + " " + tt.type.to_s
                                      if tt.type == $consts["GROUP"] or tt.type == $consts["CALL"] or tt.type == $consts["NEW_WITH_ARGS"]
                                         puts "Breaking..."
                                         break
                                         end    
                                end
                                if tt != $consts["GROUP"]
                                        n = operands.last
                                        if n[1].type != $consts["COMMA"]
                                                n2 = n[1]
                                                n[1] = Node.new(t, $consts["LIST"])
                                                n[1].push(n2)
                                        else
                                                n[1].type = $consts["LIST"]
                                        end
                                end
                                x.parenLevel -= 1
                                
                        # Automatic semicolon insertion means we may scan across a newline
                        # and into the beginning of another statement.  If so, break out of
                        # the while loop and let the t.scanOperand logic handle errors.
                        else
                                throw :gotoloop, true
                end
        end

end
end

        raise SyntaxError.new("Missing : after ?", t) if x.hookLevel != hl
        raise SyntaxError.new("Missing operand", t) if t.scanOperand
                
        # Resume default mode, scanning for operands, not operators.
        t.scanOperand = true
        puts "Ungetting"
        puts t.unget
        reduce(operators, operands, t) while operators.length > 0
        return operands.pop
end

def parse (source, filename, line = 1)
        t = Tokenizer.new(source, filename, line)
        x = CompilerContext.new(false)
        n = Script(t, x)
        raise SyntaxError.new("Syntax error", t) if !t.done
    return n
end

